AnnoyScript

The fastest way to work with arrays

Β· 6 minutes of read Β·

views

Although my grandma liked to spend time feeding me sweets and telling lies, such as the claim that all methods in V8 are equal in performance, I knew that she was secretly hiding the truth of real JavaScript ninja techniques.πŸ₯·πŸ».

Even though the cake was delicious, it's important to know that a true Saiyan always sprinkles when he tinkles[1], and the real ninja always assumes that JavaScript's mysterious engines were written by triple agents who made their stuff so confusing just as a test of our intellectual superiority and endurance before we can truly enter the Ninja realm.

Seting up the task

Imagine the situation where we want to have a workout array that contains all of our daily push-ups. And we do a lot of them. Like milions of them, and every push-up it's actually a pretty heavy-lifting for a memory.

The go-to approach by laydev would be something like this:

const workout = [];

for (let i; i < 10000000; i++) {
workout.push("push-up");
}

Looks okay, but it's actually quite slow. And don't even try to tell me that "everything will be fixed by a V8 engine that will magically turn this lazy-ass code into performance beast". We are not in the GCC town; it's the JavaScript ghetto! Things just don't get smoothed out here so easily.

Let's find a better, totally ninja-style, blazingly fast secret technique. But first, we need to reveal the first truth about JavaScript's arrays.

Empty array vs prepared array

Should we start our workout with an empty mind or filled with memories of the times we failed as a developer? Or, to stay on track with the main quest, should we just create an empty workout array, or is it better to have it filled with empty slots ready to get sweatyπŸ’¦?

const workout_empty = [];
const workout_slot_ready = ["slot",...,"slot"] // 10000000 slots

for (let i = 0; i < 10000000; i++) {
workout_empty[i] = "push-up";
}

for (let i = 0; i < 10000000; i++) {
workout_slot_ready[i] = "push-up";
}

The rule of thumb is that pushing that many elements to a completely empty array will be very slow. JavaScript generally prefers when the array is already populated[2], but let's actually test it.

The results exceed expectations. Pushing into the workout_slot_ready array is more than 23x faster!

Slot_ready Empty
πŸ† 93 ops/s 4.1 ops/s

Quickest way to create filled array

So now we know that it's better to work with filled arrays, but how to create them quick? To find out let's set up the octagon for a fight between four opponents:

Let's ask them to do 10 000 000 push-ups and see who will win in terms of performance.

// creating an empty array and filling it ol' style!
const workout = [];
for (let i = 0; i < 10000000; i++) {
workout[i] = "slot";
}

// creating an empty array and filling it up using a push
workout = [];
for (let i = 0; i < 10000000; i++) {
workout.push("slot");
}

// using Array's fill method to fill the array
workout = new Array(10000000).fill("slot", 0, 10000000);
// = new Array(10000000) performs exactly the same

// using Array's static from method with an object literal
workout = Array.from({ length: 10000000 }, () => "slot");

The results: new Array().fill is 4.8x faster than push and then almost 14x faster than Array.from() creep!

new Array().fill push() Array[i] Array.from()
πŸ† 18 ops/s 3.7 ops/s 3.5 ops/s 1.3 ops/s

Quickest way to insert elements into array

So let's assume that our workout array is already blazingly-fast-ninja-style-filledπŸ₯·πŸ»βš‘.

Now let's push some push-ups into the workout! By doing so, we need to approach a well-know JavaScript battleground of fight between forEach and for βš”οΈ.

// imperative approach
for (let i = 0; i < 10000000; i++) {
workout[i] = "push-up";
}

// declarative approach
workout.forEach((_, i) => workout[i] = "push-up");

This might cause some declarative devs to cry a little in the corner, but face the truth! Your fighting techniques are lame-o. Imperative 4 life, with performance 12.8x faster than declarative.

for forEach
πŸ† 86 ops/s 6.7 ops/s

Final comparison

Let's lay the groundwork for one final performance battle: the laydev technique from the beginning of our training and a ninja-style crafted method, versus a whole bunch of push-ups πŸ’ͺ🏻!

// 🐌 laydev style
const workout = [];

for (let i = 0; i < 10000000; i++) {
workout.push("push-up");
}

// πŸ₯·πŸ» ninja style
workout = new Array(10000000).fill("slot", 0, 10000000);

for (let i = 0; i < 10000000; i++) {
workout[i] = "push-up";
}

The results are just awesome - we are 4.2x faster![3] πŸŽ‰

Ninja styla Lay style
πŸ† 17 ops/s 4 ops/s

Tip

Next time you set yourself up for a good ninja push-up training, leave that fancy declarative kimono at home. Dress properly in ninja-imperative shinobi shōzoku outfit and remember to warm-up your workout array with some fill method[4] and use of good, ol' for loop.






  1. RIP in peace Akira Toriyama. β†©οΈŽ

  2. If you are keen to learn why is it like this check this blog. β†©οΈŽ

  3. It's worth mentioning that those results were measured using V8 browser (which is currently covering 75.5% of the market). SpiderMonkey actually makes ninja style slower than laydev (with 3.04% of the market). β†©οΈŽ

  4. Actually clean new Array(10000000) performs excatly as efficient as the one with fill, so this step could be potentially skipped (although this way we might be creating holes in the array). β†©οΈŽ

Ninja tricksJavaScript